package cn.changgou.search.service.impl;

import cn.changgou.search.dao.SkuEsMapper;
import cn.changgou.entity.Result;
import cn.changgou.goods.feign.SkuFeign;
import cn.changgou.goods.pojo.Sku;
import cn.changgou.search.pojo.SkuInfo;
import cn.changgou.search.service.SkuService;
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.*;

@Service
public class SkuServiceImpl implements SkuService {

    @Autowired
    private SkuFeign skuFeign;

    @Autowired
    private SkuEsMapper skuEsMapper;

    /*分组查询*/
    public Map<String, Object> searchGroupList(NativeSearchQueryBuilder nativeSearchQueryBuilder, Map<String, String> searchMap){
        if (searchMap == null || StringUtils.isEmpty(searchMap.get("category"))) {
            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("skuCategory").field("categoryName"));
        }
        if (searchMap == null || StringUtils.isEmpty(searchMap.get("brand"))) {
            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("skuBrand").field("brandName"));
        }
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("skuSpec").field("spec.keyword"));
        AggregatedPage<SkuInfo> aggregatedPage = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class);
        //定义一个Map，存储所有分组结果
        Map<String, Object> groupMapResult = new HashMap<>();
        if (searchMap == null || StringUtils.isEmpty(searchMap.get("category"))) {
            StringTerms categoryTerms = aggregatedPage.getAggregations().get("skuCategory");
            List<String> categoryList = getGroupList(categoryTerms);
            groupMapResult.put("categoryList", categoryList);
        }
        if (searchMap == null || StringUtils.isEmpty(searchMap.get("brand"))) {
            StringTerms brandTerms = aggregatedPage.getAggregations().get("skuBrand");
            List<String> brandList = getGroupList(brandTerms);
            groupMapResult.put("brandList", brandList);
        }
        StringTerms specTerms = aggregatedPage.getAggregations().get("skuSpec");
        List<String> specList = getGroupList(specTerms);
        Map<String, Set<String>> specMap = putAllSpec(specList);
        groupMapResult.put("specList", specMap);
        return groupMapResult;
    }

    public List<String> getGroupList(StringTerms stringTerms){
        List<String> list = new ArrayList<>();
        for (StringTerms.Bucket bucket : stringTerms.getBuckets()) {
            String fieldName = bucket.getKeyAsString();//其中的一个分类名字
            list.add(fieldName);
        }
        return list;
    }

    /*ElasticsearchTemplate:可以实现索引库的增删改查[高级搜索]*/
    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    /*规格汇总合并*/
    public Map<String, Set<String>> putAllSpec(List<String> specList){
        Map<String, Set<String>> allSpec = new HashMap<>();
        //1.循环specList
        for (String spec : specList) {
            //2.将每个JSON字符串转换成Map spec={"电视音响效果":"环绕","电视屏幕尺寸":"60英寸","尺码":"175"}
            Map<String, String> specMap = JSON.parseObject(spec, Map.class);
            //将每个Map对象合成一个Map<String, Set<String>>
            //3.合并流程：循环所有Map-取出当前Map，并且获取对应Key和Value-将当前循环的数据合并到一个Map<String, Set<String>>
            for (Map.Entry<String, String> entry : specMap.entrySet()) {
                String key = entry.getKey();//规格名字
                String value = entry.getValue();//规格值
                //从allSpec中获取当前规格对应的Set集合数据
                Set<String> specSet = allSpec.get(key);
                if (specSet == null){
                    //之前allSpec中没有该规格
                    specSet = new HashSet<>();
                }
                specSet.add(value);
                allSpec.put(key, specSet);
            }
        }
        return allSpec;
    }

    /*规格分组查询*/
    public Map<String, Set<String>> searchSpecList(NativeSearchQueryBuilder nativeSearchQueryBuilder){
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("skuSpec").field("spec.keyword").size(10000));
        AggregatedPage<SkuInfo> aggregatedPage = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class);
        StringTerms stringTerms = aggregatedPage.getAggregations().get("skuSpec");
        List<String> specList = new ArrayList<>();
        for (StringTerms.Bucket bucket : stringTerms.getBuckets()) {
            String specName = bucket.getKeyAsString(); //{"电视音响效果":"环绕","电视屏幕尺寸":"60英寸","尺码":"175"}
            specList.add(specName);
        }
        //规格汇总合并
        return putAllSpec(specList);
    }

    /*品牌分组查询*/
    public List<String> searchBrandList(NativeSearchQueryBuilder nativeSearchQueryBuilder){
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("skuBrand").field("brandName"));
        AggregatedPage<SkuInfo> aggregatedPage = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class);
        StringTerms stringTerms = aggregatedPage.getAggregations().get("skuBrand");
        List<String> brandList = new ArrayList<>();
        for (StringTerms.Bucket bucket : stringTerms.getBuckets()) {
            String brandName = bucket.getKeyAsString();
            brandList.add(brandName);
        }
        return brandList;
    }

    /*分类分组查询*/
    public List<String> searchCategoryList(NativeSearchQueryBuilder nativeSearchQueryBuilder){
        /*分组查询分类集合
         * .addAggregation():添加一个聚合操作
         * 参数1取别名
         * 参数2表示根据哪个域进行分组*/
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("skuCategory").field("categoryName"));
        AggregatedPage<SkuInfo> aggregatedPage = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class);
        /*获取分组数据
         * aggregatedPage.getAggregations()：获取的是集合，可以根据多个域进行分组
         * .get("skuCategory")：获取指定域的集合数*/
        StringTerms stringTerms = aggregatedPage.getAggregations().get("skuCategory");
        List<String> categoryList = new ArrayList<>();
        for (StringTerms.Bucket bucket : stringTerms.getBuckets()) {
            String categoryName = bucket.getKeyAsString();//其中的一个分类名字
            categoryList.add(categoryName);
        }
        return categoryList;
    }

    /*集合搜索*/
    public Map<String, Object> searchList(NativeSearchQueryBuilder nativeSearchQueryBuilder){
        //高亮配置
        HighlightBuilder.Field field = new HighlightBuilder.Field("name");//指定高亮域
        //<em style="color:red;"></em>
        field.preTags("<em style=\"color:red;\">");
        field.postTags("</em>");
        //碎片长度，关键词数据的长度
        field.fragmentSize(100);
        //添加高亮
        nativeSearchQueryBuilder.withHighlightFields(field);
        /*执行搜索，响应结果
         * 1.搜索条件封装对象
         * 2.搜索的结果集（集合数据）需要转换的类型
         * 3.AggregatedPage<SkuInfo>:搜索结果集的封装*/
        //AggregatedPage<SkuInfo> page = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class);
        AggregatedPage<SkuInfo> page = elasticsearchTemplate
                .queryForPage(
                        nativeSearchQueryBuilder.build(),//搜索条件封装
                        SkuInfo.class,//数据集合要转换的类型的字节码
                        new SearchResultMapper() {
                            @Override
                            public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
                                //存储所有转换后的高亮数据对象
                                List<T> list = new ArrayList<>();
                                //执行查询，获取所有数据->结果集[非高亮数据|高亮数据]
                                for (SearchHit hit : searchResponse.getHits()) {
                                    //分析结果集数据，获取[非高亮]数据
                                    SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
                                    //分析结果集数据，获取[高亮]数据
                                    HighlightField highlightField = hit.getHighlightFields().get("name");
                                    if (highlightField != null && highlightField.getFragments() != null){
                                        //读取高亮数据
                                        Text[] fragments = highlightField.getFragments();
                                        StringBuffer buffer = new StringBuffer();
                                        for (Text fragment : fragments) {
                                            buffer.append(fragment.toString());
                                        }
                                        //非高亮数据中指定的域替换成高亮数据
                                        skuInfo.setName(buffer.toString());

                                    }
                                    //将高亮数据添加到集合中
                                    list.add((T) skuInfo);
                                }
                                /*将数据返回
                                * 1.搜索的集合数据：携带高亮List<T> content
                                * 2.分页对象信息：Pageable pageable
                                * 3.搜索记录的总条数：long total*/
                                return new AggregatedPageImpl<T>(list, pageable, searchResponse.getHits().getTotalHits());
                            }
                        });//执行搜索后，将数据结果集封装到该对象中
        //分页参数-总记录数
        long totalElements = page.getTotalElements();
        //总页数
        int totalPages = page.getTotalPages();
        //获取数据结果集
        List<SkuInfo> content = page.getContent();
        //封装一个Map存储所有数据，并返回
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("rows", content);
        resultMap.put("total", totalElements);
        resultMap.put("totalPages", totalPages);
        //获取搜索封装信息
        NativeSearchQuery query = nativeSearchQueryBuilder.build();
        Pageable pageable = query.getPageable();
        int pageSize = pageable.getPageSize();
        int pageNumber = pageable.getPageNumber();
        //分页数据
        resultMap.put("pageSize", pageSize);
        resultMap.put("pageNumber", pageNumber);

        return resultMap;
    }

    /*搜索条件封装*/
    public NativeSearchQueryBuilder buildBasicQuery(Map<String, String> searchMap){
        //NativeSearchQueryBuilder:搜索条件构建对象，主要用于封装各种搜索条件
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        //BoolQuery
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        if (searchMap != null && searchMap.size() > 0){
            //根据关键词搜索
            String keywords = searchMap.get("keywords");
            //如果关键词不为空，则搜索关键词数据
            if (!StringUtils.isEmpty(keywords)){
                //nativeSearchQueryBuilder.withQuery(QueryBuilders.queryStringQuery(keywords).field("name"));
                boolQueryBuilder.must(QueryBuilders.queryStringQuery(keywords).field("name"));
            }
            //输入分类
            if (!StringUtils.isEmpty(searchMap.get("category"))){
                boolQueryBuilder.must(QueryBuilders.termQuery("categoryName", searchMap.get("category")));
            }
            //输入品牌
            if (!StringUtils.isEmpty(searchMap.get("brand"))){
                boolQueryBuilder.must(QueryBuilders.termQuery("brandName", searchMap.get("brand")));
            }
            //规格过滤
            for (Map.Entry<String, String> entry : searchMap.entrySet()) {
                String key = entry.getKey();
                //如果key以spec_开始，则表示规格筛选查询
                if (key.startsWith("spec_")){
                    //规格条件的值
                    String value = entry.getValue();
                    //spec_网络
                    boolQueryBuilder.must(QueryBuilders.termQuery("specMap." + key.substring(5) + ".keyword", value));
                }
            }
            String price = searchMap.get("price");
            if (!StringUtils.isEmpty(price)){
                price = price.replace("元", "").replace("以上", "");
                String[] prices = price.split("-");
                if (prices.length > 0){
                    boolQueryBuilder.must(QueryBuilders.rangeQuery("price").gt(Integer.parseInt(prices[0])));
                    if (prices.length == 2){
                        boolQueryBuilder.must(QueryBuilders.rangeQuery("price").lte(Integer.parseInt(prices[1])));
                    }
                }
            }
            String sortField = searchMap.get("sortField");//指定排序的域
            String sortRule = searchMap.get("sortRule");//指定排序的规则
            if (!StringUtils.isEmpty(sortField) && !StringUtils.isEmpty(sortRule)){
                nativeSearchQueryBuilder.withSort(new FieldSortBuilder(sortField).order(SortOrder.valueOf(sortRule)));
            }
        }
        //分页，用户如果不传分页参数，默认第一页
        Integer pageNum = coverterPage(searchMap);
        Integer size = 30;
        nativeSearchQueryBuilder.withPageable(PageRequest.of(pageNum - 1, size));
        //将boolQueryBuilder填充给nativeSearchQueryBuilder
        return nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
    }

    /*接收前端传入的分页参数*/
    public Integer coverterPage(Map<String, String> searchMap){
        if (searchMap != null){
            String pageNum = searchMap.get("pageNum");
            try {
                return Integer.parseInt(pageNum);
            }catch (Exception e){

            }
        }
        return 1;
    }

    /*多条件搜索*/
    @Override
    public Map<String, Object> search(Map<String, String> searchMap) {
        //搜索条件封装
        NativeSearchQueryBuilder nativeSearchQueryBuilder = buildBasicQuery(searchMap);
        //集合搜索
        Map<String, Object> resultMap = searchList(nativeSearchQueryBuilder);
        /*//当用户选择了分类，将分类作为搜索条件，则不需要对分类进行分组搜索，因为分组搜索的数据是用于显示分类搜索条件的
        if (searchMap == null || StringUtils.isEmpty(searchMap.get("category"))){
            //分类分组查询实现
            List<String> categoryList = searchCategoryList(nativeSearchQueryBuilder);
            resultMap.put("categoryList", categoryList);
        }
        //当用户选择了分类，将分类作为搜索条件，则不需要对分类进行分组搜索，因为分组搜索的数据是用于显示分类搜索条件的
        if (searchMap == null || StringUtils.isEmpty(searchMap.get("brand"))){
            //查询品牌集合[搜索条件]
            List<String> brandList = searchBrandList(nativeSearchQueryBuilder);
            resultMap.put("brandList", brandList);
        }
        //规格查询
        Map<String, Set<String>> specList = searchSpecList(nativeSearchQueryBuilder);
        resultMap.put("specList", specList);*/
        Map<String, Object> groupMap = searchGroupList(nativeSearchQueryBuilder, searchMap);
        resultMap.putAll(groupMap);
        return resultMap;
    }

    /*导入索引库*/
    @Override
    public void importData() {
        //Feign调用，查询List<Sku>
        Result<List<Sku>> skuResult = skuFeign.findAll();
        //将List<Sku>转成List<SkuInfo>
        List<SkuInfo> skuInfoList = JSON.parseArray(JSON.toJSONString(skuResult.getData()), SkuInfo.class);
        //调用Dao实现数据批量导入
        for (SkuInfo skuInfo : skuInfoList) {
            //获取spec -> Map(String) -> Map类型
            Map<String, Object> specMap = JSON.parseObject(skuInfo.getSpec(), Map.class);
            //如果需要生成动态的域，只需要将该域存入到一个Map<String, Object>对象中即可，该Map<String, Object>的key会生成一个域，域的名字为该Map的key
            //当前Map<String, Object>后面Object的值会作为当前Sku对象域(key)对应的值
            skuInfo.setSpecMap(specMap);
        }
        skuEsMapper.saveAll(skuInfoList);
    }
}
